package com.im.project.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.im.common.constant.Constant;
import com.im.common.enums.MessageTypeEnum;
import com.im.common.enums.ResultEnum;
import com.im.common.enums.WsEnum;
import com.im.common.exception.BusinessException;
import com.im.common.util.GenerateIdUtil;
import com.im.project.entity.*;
import com.im.project.entity.dto.AddOrRemoveClusterUserDto;
import com.im.project.entity.dto.ClusterIdDto;
import com.im.project.entity.dto.CreateClusterDto;
import com.im.project.entity.dto.UpdateClusterDto;
import com.im.project.entity.vo.*;
import com.im.project.mapper.*;
import com.im.project.rabbitmq.RabbitmqProducer;
import com.im.project.service.ClusterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 群组 服务层接口实现类
 *
 * @author yunZhouJiaDi
 */
@Slf4j
@Service
public class ClusterServiceImpl extends ServiceImpl<ClusterMapper, Cluster> implements ClusterService {

    @Resource
    private ClusterMapper clusterMapper;

    @Resource
    private ClusterUserMapper clusterUserMapper;

    @Resource
    private ChatMapper chatMapper;

    @Resource
    private ChatUserMapper chatUserMapper;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ChatMessageMapper chatMessageMapper;

    @Resource
    private RabbitmqProducer rabbitmqProducer;

    @Override
    @Transactional
    public Boolean createCluster(CreateClusterDto createClusterDto) {
        Cluster cluster = clusterMapper.selectOne(new QueryWrapper<Cluster>().eq("name", createClusterDto.getName()));
        if (!ObjectUtils.isEmpty(cluster)) {
            throw new BusinessException(ResultEnum.CLUSTER_NAME_ALREADY_EXISTS.getCode(), ResultEnum.CLUSTER_NAME_ALREADY_EXISTS.getMessage());
        }
        String userId = StpUtil.getLoginIdAsString();
        String clusterId = GenerateIdUtil.generateClusterId();
        String chatId = GenerateIdUtil.generateClusterChatId(clusterId);
        Date date = new Date();
        ChatMessage chatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(GenerateIdUtil.generateMessageId()).
                setMessageType(MessageTypeEnum.COMMON.getName()).
                setMessageContent(Constant.SystemConstant.CREATE_CLUSTER_SUCCESS).
                setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                setSendUserId(null).
                setSendUsername(null).
                setSendUserIcon(null).
                setSendTime(date.getTime()).
                setContactId(clusterId).
                setContactUsername(createClusterDto.getName()).
                setContactUserIcon(createClusterDto.getIcon()).
                setMsgWithdrawn(ResultEnum.MSG_WITHDRAWN_NOT.getCode());
        Cluster addCluster = new Cluster().
                setClusterId(clusterId).
                setName(createClusterDto.getName()).
                setIcon(createClusterDto.getIcon()).
                setOwnerId(userId).
                setCreateTime(date).
                setNotice(createClusterDto.getNotice()).
                setJoinType(createClusterDto.getJoinType()).
                setMute(ResultEnum.WITHOUT_HESITATION.getCode()).
                setStatus(ResultEnum.CLUSTER_NORMAL.getCode());
        clusterMapper.insert(addCluster);
        redisTemplate.opsForValue().set(Constant.RedisConstant.CLUSTER + clusterId, addCluster);
        clusterUserMapper.insert(
                new ClusterUser().
                        setClusterId(clusterId).
                        setUserId(userId).
                        setRole(ResultEnum.GROUP_LEADER.getCode()).
                        setCreateTime(date)
        );
        redisTemplate.opsForList().leftPush(Constant.RedisConstant.CLUSTER_USER + clusterId, userId);
        chatMapper.insert(
                new Chat().
                        setChatId(chatId).
                        setLastMessage(Constant.SystemConstant.CREATE_CLUSTER_SUCCESS).
                        setLastReceiveTime(date.getTime())
        );
        chatUserMapper.insert(
                new ChatUser().
                        setChatId(chatId).
                        setUserId(userId).
                        setContactId(clusterId).
                        setContactName(createClusterDto.getName())
        );
        chatMessageMapper.insert(chatMessage);
        rabbitmqProducer.sendMessageProducer(
                Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                new MessageVo<ChatMessage>().
                        setCommandCode(WsEnum.CLUSTER_CREATE.getCode()).
                        setCommandName(WsEnum.CLUSTER_CREATE.getMessage()).
                        setContactId(userId).
                        setData(chatMessage)
        );
        return true;
    }

    @Override
    public List<SearchGroupVo> queryGroup(String searchKey) {
        if (searchKey != null && !searchKey.isEmpty()) {
            List<Cluster> list = clusterMapper.selectList(new QueryWrapper<Cluster>()
                    .eq("status", ResultEnum.CLUSTER_NORMAL.getCode())
                    .like("cluster_id", searchKey)
                    .or()
                    .eq("status", ResultEnum.CLUSTER_NORMAL.getCode())
                    .like("name", searchKey));
            List<SearchGroupVo> arrayList = new ArrayList<>();
            list.forEach(cluster -> {
                SearchGroupVo searchGroupVo = new SearchGroupVo().
                        setContactId(cluster.getClusterId()).
                        setContactName(cluster.getName()).
                        setContactIcon(cluster.getIcon());
                List<ClusterUser> clusterUserList = clusterUserMapper.selectList(
                        new QueryWrapper<ClusterUser>().
                                eq("cluster_id", cluster.getClusterId()).
                                eq("user_id", StpUtil.getLoginIdAsString()));
                if (clusterUserList.size() > 0) {
                    searchGroupVo.setStatus(1);
                } else {
                    searchGroupVo.setStatus(null);
                }
                arrayList.add(searchGroupVo);
            });
            return arrayList;
        } else {
            return new ArrayList<SearchGroupVo>();
        }
    }

    @Override
    public List<Cluster> queryCurrentClusterList() {
        return clusterMapper.selectList(new QueryWrapper<Cluster>().eq("owner_id", StpUtil.getLoginIdAsString()).eq("status", ResultEnum.FRIEND_CONTACT_STATUS.getCode()));
    }

    @Override
    public List<MyClusterVo> queryGroupListByUserId() {
        return clusterMapper.queryGroupListByUserId(StpUtil.getLoginIdAsString());
    }

    @Override
    public ClusterVo queryClusterByClusterId(String clusterId) {
        return clusterMapper.queryClusterByClusterId(clusterId);
    }

    @Override
    @Transactional
    public Boolean updateCluster(UpdateClusterDto updateClusterDto) {
        Cluster beforeCluster = (Cluster) redisTemplate.opsForValue().get(Constant.RedisConstant.CLUSTER + updateClusterDto.getClusterId());
        Cluster cluster = inspectCluster(updateClusterDto.getClusterId());
        clusterMapper.updateById(
                cluster.
                        setIcon(updateClusterDto.getIcon()).
                        setJoinType(updateClusterDto.getJoinType()).
                        setName(updateClusterDto.getName()).
                        setNotice(updateClusterDto.getNotice()).
                        setMute(updateClusterDto.getMute())
        );
        redisTemplate.delete(Constant.RedisConstant.CLUSTER + updateClusterDto.getClusterId());
        Cluster redisCluster = clusterMapper.selectOne(new QueryWrapper<Cluster>().eq("cluster_id", updateClusterDto.getClusterId()));
        redisTemplate.opsForValue().set(Constant.RedisConstant.CLUSTER + updateClusterDto.getClusterId(), redisCluster);
        if (!updateClusterDto.getName().equalsIgnoreCase(beforeCluster.getName()) || !updateClusterDto.getIcon().equalsIgnoreCase(beforeCluster.getIcon())) {
            List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + updateClusterDto.getClusterId(), 0, -1);
            contactIdList.forEach(contactId -> {
                rabbitmqProducer.sendMessageProducer(
                        Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                        new MessageVo<UpdateMessageVo>().
                                setCommandCode(WsEnum.CLUSTER_UPDATE.getCode()).
                                setCommandName(WsEnum.CLUSTER_UPDATE.getMessage()).
                                setContactId(contactId).
                                setData(
                                        new UpdateMessageVo().
                                                setUserId(contactId).
                                                setName(updateClusterDto.getName()).
                                                setIcon(updateClusterDto.getIcon()).
                                                setContactId(updateClusterDto.getClusterId())
                                )
                );
            });
            chatUserMapper.update(
                    new ChatUser().setContactName(updateClusterDto.getName()),
                    new QueryWrapper<ChatUser>().eq("contact_id", updateClusterDto.getClusterId())
            );
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean leaveCluster(ClusterIdDto clusterIdDto) {
        Cluster cluster = (Cluster) redisTemplate.opsForValue().get(Constant.RedisConstant.CLUSTER + clusterIdDto.getClusterId());
        if (ObjectUtils.isEmpty(cluster)) {
            cluster = clusterMapper.selectOne(new QueryWrapper<Cluster>().eq("cluster_id", clusterIdDto.getClusterId()));
            redisTemplate.opsForValue().set(Constant.RedisConstant.CLUSTER + clusterIdDto.getClusterId(), cluster);
        }
        UserVo userVo = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + StpUtil.getLoginIdAsString());
        String chatId = GenerateIdUtil.generateClusterChatId(clusterIdDto.getClusterId());
        Date date = new Date();
        String messageContent = String.format(Constant.SystemConstant.EXIT_CLUSTER_CONTENT, "`" + userVo.getUsername() + "`" + " ");
        ChatMessage chatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(GenerateIdUtil.generateMessageId()).
                setMessageType(MessageTypeEnum.COMMON.getName()).
                setMessageContent(messageContent).
                setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                setSendUserId(null).
                setSendUsername(null).
                setSendUserIcon(null).
                setSendTime(date.getTime()).
                setContactId(clusterIdDto.getClusterId()).
                setContactUsername(cluster.getName()).
                setContactUserIcon(cluster.getIcon()).
                setMsgWithdrawn(ResultEnum.MSG_WITHDRAWN_NOT.getCode());
        chatMessageMapper.insert(chatMessage);
        List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + clusterIdDto.getClusterId(), 0, -1);
        chatMessage.setMemberCount(contactIdList.size() - 1);
        contactIdList.forEach(contactId -> {
            rabbitmqProducer.sendMessageProducer(
                    Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                    new MessageVo<ChatMessage>().
                            setCommandCode(WsEnum.LEAVE_CLUSTER.getCode()).
                            setCommandName(WsEnum.LEAVE_CLUSTER.getMessage()).
                            setContactId(contactId).
                            setData(chatMessage)
            );
        });
        clusterUserMapper.delete(new QueryWrapper<ClusterUser>().eq("cluster_id", clusterIdDto.getClusterId()).eq("user_id", StpUtil.getLoginIdAsString()));
        redisTemplate.opsForList().remove(Constant.RedisConstant.CLUSTER_USER + clusterIdDto.getClusterId(), 1, StpUtil.getLoginIdAsString());
        chatUserMapper.delete(new QueryWrapper<ChatUser>().eq("contact_id", clusterIdDto.getClusterId()).eq("user_id", StpUtil.getLoginIdAsString()));
        chatMapper.update(
                new Chat().setLastMessage(messageContent).setLastSendName("").setLastReceiveTime(date.getTime()),
                new QueryWrapper<Chat>().eq("chat_id", chatId)
        );
        return true;
    }

    @Override
    @Transactional
    public Boolean dissolutionCluster(ClusterIdDto clusterIdDto) {
        Cluster cluster = inspectCluster(clusterIdDto.getClusterId());
        String chatId = GenerateIdUtil.generateClusterChatId(clusterIdDto.getClusterId());
        Date date = new Date();
        ChatMessage chatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(GenerateIdUtil.generateMessageId()).
                setMessageType(MessageTypeEnum.COMMON.getName()).
                setMessageContent(String.format(Constant.SystemConstant.CANCEL_CLUSTER_CONTENT)).
                setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                setSendUserId(null).
                setSendUsername(null).
                setSendUserIcon(null).
                setSendTime(date.getTime()).
                setContactId(clusterIdDto.getClusterId()).
                setContactUsername(cluster.getName()).
                setContactUserIcon(cluster.getIcon()).
                setMsgWithdrawn(ResultEnum.MSG_WITHDRAWN_NOT.getCode());
        chatMessageMapper.insert(chatMessage);
        List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + clusterIdDto.getClusterId(), 0, -1);
        contactIdList.forEach(contactId -> {
            rabbitmqProducer.sendMessageProducer(
                    Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                    new MessageVo<ChatMessage>().
                            setCommandCode(WsEnum.DISSOLUTION_CLUSTER.getCode()).
                            setCommandName(WsEnum.DISSOLUTION_CLUSTER.getMessage()).
                            setContactId(contactId).
                            setData(chatMessage)
            );
            clusterUserMapper.delete(new QueryWrapper<ClusterUser>().eq("cluster_id", clusterIdDto.getClusterId()).eq("user_id", contactId));
            chatUserMapper.delete(new QueryWrapper<ChatUser>().eq("contact_id", clusterIdDto.getClusterId()).eq("user_id", contactId));
        });
        clusterMapper.updateById(cluster.setStatus(ResultEnum.CLUSTER_DISSOLUTION.getCode()));
        chatMapper.update(
                new Chat().setLastMessage(String.format(Constant.SystemConstant.CANCEL_CLUSTER_CONTENT)).setLastSendName("").setLastReceiveTime(date.getTime()),
                new QueryWrapper<Chat>().eq("chat_id", chatId)
        );
        redisTemplate.delete(Constant.RedisConstant.CLUSTER + clusterIdDto.getClusterId());
        redisTemplate.delete(Constant.RedisConstant.CLUSTER_USER + clusterIdDto.getClusterId());
        return true;
    }

    @Override
    public ClusterUserChatVo queryClusterUserChatByClusterId(String clusterId) {
        Cluster beforeCluster = (Cluster) redisTemplate.opsForValue().get(Constant.RedisConstant.CLUSTER + clusterId);
        return new ClusterUserChatVo().
                setCluster(beforeCluster).
                setClusterUserVoList(clusterMapper.queryClusterUserList(clusterId));
    }

    @Override
    @Transactional
    public Boolean addClusterUsers(AddOrRemoveClusterUserDto addOrRemoveClusterUserDto) {
        Cluster cluster = inspectCluster(addOrRemoveClusterUserDto.getClusterId());
        String chatId = GenerateIdUtil.generateClusterChatId(addOrRemoveClusterUserDto.getClusterId());
        ArrayList<String> usernameList = new ArrayList<>();
        Date date = new Date();
        addOrRemoveClusterUserDto.getIdList().forEach(userId -> {
            UserVo userVo = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + userId);
            usernameList.add(userVo.getUsername());
            clusterUserMapper.insert(
                    new ClusterUser().setClusterId(addOrRemoveClusterUserDto.getClusterId()).
                            setUserId(userId).
                            setRole(ResultEnum.ORDINARY_MEMBERS.getCode()).
                            setCreateTime(date)
            );
            redisTemplate.opsForList().leftPush(Constant.RedisConstant.CLUSTER_USER + addOrRemoveClusterUserDto.getClusterId(), userId);
            ChatUser chatUser = chatUserMapper.selectOne(new QueryWrapper<ChatUser>().eq("user_id", userId).eq("contact_id", addOrRemoveClusterUserDto.getClusterId()));
            if (ObjectUtils.isEmpty(chatUser)) {
                chatUserMapper.insert(
                        new ChatUser().
                                setChatId(chatId).
                                setUserId(userId).
                                setContactId(addOrRemoveClusterUserDto.getClusterId()).
                                setContactName(cluster.getName())
                );
            } else {
                chatUserMapper.updateById(
                        chatUser.
                                setChatId(chatId).
                                setUserId(userId).
                                setContactId(addOrRemoveClusterUserDto.getClusterId()).
                                setContactName(cluster.getName())
                );
            }
        });
        String result = usernameList.stream().map(String::valueOf).collect(Collectors.joining(","));
        chatMapper.update(
                new Chat().setLastReceiveTime(date.getTime()).setLastSendName("").setLastMessage(String.format(Constant.SystemConstant.ADD_CLUSTER, "`" + result + "`" + " ")),
                new QueryWrapper<Chat>().eq("chat_id", chatId)
        );
        ChatMessage addChatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(GenerateIdUtil.generateMessageId()).
                setMessageType(MessageTypeEnum.COMMON.getName()).
                setMessageContent(String.format(Constant.SystemConstant.ADD_CLUSTER, "`" + result + "`" + " ")).
                setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                setSendUserId(null).
                setSendUsername(null).
                setSendUserIcon(null).
                setSendTime(date.getTime()).
                setContactId(addOrRemoveClusterUserDto.getClusterId()).
                setContactUsername(cluster.getName()).
                setContactUserIcon(cluster.getIcon()).
                setMsgWithdrawn(ResultEnum.MSG_WITHDRAWN_NOT.getCode());
        chatMessageMapper.insert(addChatMessage);
        List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + addOrRemoveClusterUserDto.getClusterId(), 0, -1);
        addChatMessage.setMemberCount(contactIdList.size());
        contactIdList.forEach(contactId -> {
            rabbitmqProducer.sendMessageProducer(
                    Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                    new MessageVo<ChatMessage>().
                            setCommandCode(WsEnum.ADD_CLUSTER.getCode()).
                            setCommandName(WsEnum.ADD_CLUSTER.getMessage()).
                            setContactId(contactId).
                            setData(addChatMessage)
            );
        });
        return true;
    }

    @Override
    @Transactional
    public Boolean delClusterUser(AddOrRemoveClusterUserDto addOrRemoveClusterUserDto) {
        Cluster cluster = inspectCluster(addOrRemoveClusterUserDto.getClusterId());
        String chatId = GenerateIdUtil.generateClusterChatId(addOrRemoveClusterUserDto.getClusterId());
        ArrayList<String> usernameList = new ArrayList<>();
        Date date = new Date();
        addOrRemoveClusterUserDto.getIdList().forEach(userId -> {
            UserVo userVo = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + userId);
            usernameList.add(userVo.getUsername());
        });
        String result = usernameList.stream().map(String::valueOf).collect(Collectors.joining(","));
        chatMapper.update(
                new Chat().setLastReceiveTime(date.getTime()).setLastSendName("").setLastMessage(String.format(Constant.SystemConstant.KICK_CLUSTER, "`" + result + "`" + " ")),
                new QueryWrapper<Chat>().eq("chat_id", chatId)
        );
        ChatMessage delChatMessage = new ChatMessage().
                setChatId(chatId).
                setMessageId(GenerateIdUtil.generateMessageId()).
                setMessageType(MessageTypeEnum.COMMON.getName()).
                setMessageContent(String.format(Constant.SystemConstant.KICK_CLUSTER, "`" + result + "`" + " ")).
                setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                setSendUserId(null).
                setSendUsername(null).
                setSendUserIcon(null).
                setSendTime(date.getTime()).
                setContactId(addOrRemoveClusterUserDto.getClusterId()).
                setContactUsername(cluster.getName()).
                setContactUserIcon(cluster.getIcon()).
                setMsgWithdrawn(ResultEnum.MSG_WITHDRAWN_NOT.getCode());
        chatMessageMapper.insert(delChatMessage);
        List<String> contactIdList = (List<String>) redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + addOrRemoveClusterUserDto.getClusterId(), 0, -1);
        delChatMessage.setMemberCount(contactIdList.size() - addOrRemoveClusterUserDto.getIdList().size());
        contactIdList.forEach(contactId -> {
            rabbitmqProducer.sendMessageProducer(
                    Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                    new MessageVo<ChatMessage>().
                            setCommandCode(WsEnum.KICK_CLUSTER.getCode()).
                            setCommandName(WsEnum.KICK_CLUSTER.getMessage()).
                            setContactId(contactId).
                            setData(delChatMessage)
            );
        });
        addOrRemoveClusterUserDto.getIdList().forEach(userId -> {
            clusterUserMapper.delete(new QueryWrapper<ClusterUser>().eq("cluster_id", addOrRemoveClusterUserDto.getClusterId()).eq("user_id", userId));
            redisTemplate.opsForList().remove(Constant.RedisConstant.CLUSTER_USER + addOrRemoveClusterUserDto.getClusterId(), 1, userId);
            chatUserMapper.delete(new QueryWrapper<ChatUser>().eq("contact_id", addOrRemoveClusterUserDto.getClusterId()).eq("user_id", userId));
        });
        return true;
    }

    private Cluster inspectCluster(String clusterId) {
        Cluster cluster = null;
        cluster = (Cluster) redisTemplate.opsForValue().get(Constant.RedisConstant.CLUSTER + clusterId);
        if (ObjectUtils.isEmpty(cluster)) {
            cluster = clusterMapper.selectOne(new QueryWrapper<Cluster>().eq("cluster_id", clusterId));
            redisTemplate.opsForValue().set(Constant.RedisConstant.CLUSTER + clusterId, cluster);
        }
        if (!cluster.getOwnerId().equalsIgnoreCase(StpUtil.getLoginIdAsString())) {
            throw new BusinessException(ResultEnum.NOT_OWNER_NOT_OPERATE.getCode(), ResultEnum.NOT_OWNER_NOT_OPERATE.getMessage());
        }
        return cluster;
    }

}
